package com.zzyl.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstants;
import com.zzyl.entity.*;
import com.zzyl.mapper.*;
import com.zzyl.utils.EmptyUtil;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static java.time.Duration.between;

/**
 * @Description AlertJob
 * @Author 聆听
 * @Date 2025-09-15
 */
@Component
@Slf4j
public class AlertJob {
    @Value("${spring.alert.managerRole:超级管理员}")
    private String managerRole;

    @Value("${spring.alert.deviceMaintainerRole:维修工}")
    private String deviceMaintainerRole;


    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private AlertDataMapper alertDataMapper;


    // @Scheduled(cron = "0/2 * * * * ?") // 每两秒钟
    @Scheduled(cron = "0 * * * * *") // 每分钟
    public void alert() {
        // 查询有哪些规则
        List<AlertRule> alertRuleList = alertRuleMapper.selectAll();

        // 判断规则是否为空
        if (EmptyUtil.isNullOrEmpty(alertRuleList)) {
            return;
        }

        // 查询所有设备，通过iotId获取每个设备的最新数据
        List<Device> devices = deviceMapper.selectAll();

        // 存储最新上报的数据
        List<DeviceData> deviceDataAllNew = new ArrayList<>();
        devices.forEach(e -> {
            Object data = redisTemplate.opsForHash().get(CacheConstants.DEVICE_LASTDATA_CACHE_KEY, e.getIotId());
            if (data != null) {
                List<DeviceData> list = JSONUtil.toList((String) data, DeviceData.class);
                // 过滤掉上报时间超过一分钟的数据，因为定时任务每分钟上报一次
                list.stream().filter(d ->
                    between(d.getAlarmTime(), LocalDateTime.now()).toMinutes() < 1
                ).forEach(deviceDataAllNew::add);
            }
        });

        // 判断是否为空
        if (EmptyUtil.isNullOrEmpty(deviceDataAllNew)) {
            return;
        }

        log.info("规则,{}", alertRuleList);
        log.info("数据,{}", deviceDataAllNew);


        // 遍历规则集，内嵌遍历数据集，是否异常
        alertRuleList.forEach(r -> {
            deviceDataAllNew.forEach(e -> {
                boolean flex = false;
                if (e.getProductKey().equals(r.getProductKey()) && e.getFunctionId().equals(r.getFunctionId())) { // 判断这条数据是否属于这个设备规则，并且是针对这个模型， 减少无意义遍历次数
                    // 触发报警条件
                    if (">=".equals(r.getOperator()) && (Integer.parseInt(e.getDataValue()) >= r.getValue())) {
                        flex = true;
                    }
                    if ("<".equals(r.getOperator()) && (Integer.parseInt(e.getDataValue()) < r.getValue())) {
                        flex = true;
                    }
                    if (flex) {
                        alertProcess(e, r);
                    }

                }
            });

        });
    }

    // 处理报警数据
    private void alertProcess(DeviceData e, AlertRule r) {

        // 判断是否处于报警生效时间段
        if (isInAlertTime(e, r)) {
            // 判断是否在沉默时间内重复报警
            if (isSilentTime(e, r)) {
                // 判断报警周期
                if (SetAlertCount(e) >= r.getAlertSilentPeriod()) {
                    // 清空报警次数，直接把Redis记录报警次数的数据删除，防止以后大量使用一次就不使用的冷数据，占用空间
                    redisTemplate.delete(CacheConstants.ALERT_Count + e.getIotId() + ":" + e.getFunctionId());
                    // 这里没有用redis设置沉默周期。判断是采用查数据库来判断的

                    // 护理员数据或者维修工
                    List<AlertData> alertDataList =null;

                    // 判断异常数据类型
                    if (e.getPhysicalLocationType() == 2 || e.getPhysicalLocationType() == -1) { // 与老人相关的设备，通知护工和超级管理员
                        List<Long> nursingIds = null;
                        if (e.getLocationType() == 1) {// 老人的固定设备，床位的睡眠检测带
                            // 通过床位id查询护理员id
                            nursingIds = deviceMapper.selectNursingIdsByIotIdWithBed(e.getAccessLocation());
                        } else {// 老人的随时设备，健康手表
                            // 通过老人id找到护理员id
                            nursingIds = deviceMapper.selectNursingIdsByIotIdWithElder(e.getAccessLocation());
                        }

                        // 构建护理员的数据对象
                        alertDataList = nursingIds.stream().map(id -> BuildAlertData(e, r, id, 0)).collect(Collectors.toList());


                    } else { // 与老人无关的设备数据，烟雾报警器，通知维修工和超级管理员
                        // 查询维修工id
                        List<Long> longs = userRoleMapper.selectByRoleName(deviceMaintainerRole);

                        // 构建维修工的数据对象
                        alertDataList = longs.stream().map(id -> BuildAlertData(e, r, id, 1)).collect(Collectors.toList());

                    }
                    // 维护工 或 护理员上面各自代码块包装了，这里还要封装公共的超级管理员
                    List<AlertData> alertDataList_admin = null;
                    try {
                        List<Long> admins = userRoleMapper.selectByRoleName(managerRole);
                        alertDataList_admin = admins.stream().map(id -> BuildAlertData(e, r, id, 0)).collect(Collectors.toList());
                    } catch (Exception ex) {
                        throw new RuntimeException(ex);
                    }

                    // 批量插入，维修工在数据库没有对应的id
                    if (!EmptyUtil.isNullOrEmpty(alertDataList)) {
                        alertDataMapper.batchInsert(alertDataList); // 护理员 或 维修工
                    }
                    try {
                        alertDataMapper.batchInsert(alertDataList_admin); // 超级管理员
                    } catch (Exception ex) {
                        throw new RuntimeException(ex);
                    }

                }
            }
        }
    }

    // 过滤报警有效时间
    private boolean isInAlertTime(DeviceData e, AlertRule r) {
        // 解析报警上报时间
        LocalTime alarmTimeLocalTime = e.getAlarmTime().toLocalTime();
        // 解析规则报警有效时间段        格式：00:00:00~23:59:59
        String[] times = r.getAlertEffectivePeriod().split("~");
        LocalTime startTime = LocalTime.parse(times[0]);
        LocalTime endTime = LocalTime.parse(times[1]);

        return !alarmTimeLocalTime.isBefore(startTime) &&
                !alarmTimeLocalTime.isAfter(endTime);
    }

    // 过滤沉默时间
    private boolean isSilentTime(DeviceData e, AlertRule r) {
        // 如果5分钟内，已经保存了这个设备相同物模型的报警数据，则不再处理，沉默周期
        AlertData alertData = alertDataMapper.selectByIotIdAndFunctionId(e.getIotId(), e.getFunctionId());
        LocalDateTime nowTime = LocalDateTime.now();
        // 判断是否需要处理,无历史数据或超过沉默周期
        return alertData == null ||
                Duration.between(alertData.getCreateTime(), nowTime).toMinutes() > r.getAlertSilentPeriod();

    }

    // Redis记录报警次数
    private int SetAlertCount(DeviceData e) {
        // Redis中设置一个报警次数
        String alertCountKey = CacheConstants.ALERT_Count + e.getIotId() + ":" + e.getFunctionId();
        Object o = redisTemplate.opsForValue().get(alertCountKey);
        int alertCount = 0;
        if (o != null) {
            alertCount = Integer.parseInt(o.toString());
        }
        // 每次增加一次
        alertCount++;
        redisTemplate.opsForValue().set(alertCountKey, alertCount);
        return alertCount - 1; // 这次不算
    }

    // 构建插入报警数据对象
    private AlertData BuildAlertData(DeviceData e, AlertRule r, Long userId, int type) {
        AlertData alertData = BeanUtil.copyProperties(e, AlertData.class);
        alertData.setAlertRuleId(r.getId());
        alertData.setAlertReason(
                r.getFunctionName() + r.getOperator() + r.getValue() + r.getDuration() + r.getAlertSilentPeriod());// 报警原因：功能名称+运算符+阈值+持续周期+聚合周
        // alertData.setProcessingResult();插入的时候是未处理状态
        // alertData.setProcessorId();
        // alertData.setProcessorName();
        // alertData.setProcessingTime(LocalDateTime.now());
        alertData.setType(type);
        alertData.setStatus(0);
        alertData.setUserId(userId);
        return alertData;
    }

}
